package test.seqaligner;

import jaligner.util.SequenceParser;
import jaligner.util.SequenceParserException;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Hashtable;

public class DatabaseHandler
{
	private int buffSize = 8192;
	private char[] buffer = new char[buffSize];
	private boolean eof = false;
	private int nbSequences = 0;
	private Hashtable<Integer, String> data = null;
	
	private static DatabaseHandler instance;
	private static String dbPath = null;
	
	public synchronized static DatabaseHandler instance(String path) throws Exception {
	    if (!path.equals(dbPath) && dbPath != null) {
		System.out.println("Loading new database: " + path);
		instance.clearDB();
		instance = null;
	    }
	    if (instance != null) {
		return instance;
	    }
	    instance = new DatabaseHandler(path, path + ".idx", null);
	    return instance;
	}
	
	/**
	 * Generate an index file for the specified database. 
	 * @param path the path to the sequence database.
	 * @param indexPath the path to the index file to generate.
	 * @param charset name of the charset to use when reading the sequence database.
	 * If null, the default charset will be used.
	 * @throws Exception if an IO error occurs.
	 */
	private DatabaseHandler(String path, String indexPath, String charset) throws Exception
	{
		String newCharset = (charset == null) ? Charset.defaultCharset().name() : charset;
		DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(indexPath)));
		Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), newCharset));
		data = new Hashtable<Integer, String>();

		    boolean end = false;
		    int seqNumber = 1;
		    while (!end){
			String s = nextSequence(dis, reader);
			if (s == null) 
			    end = true;
			else { 
			    data.put(seqNumber, s);
			    seqNumber++;
			}
		    }
		    nbSequences = seqNumber - 1;
		    this.dbPath = path;
	}

	public void clearDB(){ 
	    data.clear();
	}
	
	public String getSequence(int number){
	    return data.get(number);
	}
	
	public int getSize() {
	    return nbSequences;
	}
	
	/**
	 * Read the next sequence from the sequences database.
	 * @return the sequence as a string or null if end of file was reached.
	 * @throws Exception if an IO error occurs.
	 */
	public String nextSequence(DataInputStream dis, Reader reader) throws Exception
	{
		if (eof) return null;
		int length = 0;
		try
		{
			length = dis.readInt();
		}
		catch(EOFException e)
		{
			eof = true;
			dis.close();
			reader.close();
			return null;
		}
		if (length > buffSize)
		{
			buffSize = length;
			buffer = new char[buffSize];
		}
		int count = 0;
		while (count < length)
		{
			int n = reader.read(buffer, 0, length);
			if (n < 0) break;
			count += n;
		}
		return new String(buffer, 0, length);
	}

	/**
	 * Generate an index file for the specified database. 
	 * @param path the path to the sequence database.
	 * @param indexPath the path to the index file to generate.
	 * @param charset name of the charset to use when reading the sequence database.
	 * If null, the default charset will be used.
	 * @return the number of sequences in the database.
	 * @throws Exception if an IO error occurs.
	 */
	public static int generateIndex(String path, String indexPath, String charset) throws Exception
	{
		if (charset == null) charset = Charset.defaultCharset().name();
		Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), charset));
		DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(indexPath)));
		boolean end = false;
		int c = reader.read();
		int nbSequences = 0;
		while (!end)
		{
			int length = 0;
			if (c == '>')
			{
				length++;
				boolean lineEnd = false;
				while (!lineEnd)
				{
					c = reader.read();
					if (c == -1)
					{
						lineEnd = true;
						end = true;
					}
					else
					{
						length++;
						if (c == '\n') lineEnd = true;
					}
				}
				boolean sequenceEnd = false;
				while (!sequenceEnd)
				{
					c = reader.read();
					if ((c == '>') || (c == -1))
					{
						sequenceEnd = true;
						dos.writeInt(length);
						nbSequences++;
						//if (nbSequences % 100 == 0) System.out.println("indexed "+nbSequences+" sequences");
						if (c == -1)
						{
							end = true;
						}
					}
					else length++;
				}
			}
		}
		dos.flush();
		dos.close();
		reader.close();
		return nbSequences;
	}
	
	public static void main(String[] args) throws Exception {
	    // Generar indices de las BD
	    String SEQ_HOME = "/home/cmateos/workspace/ibisAgent-2.1";
	    String[] databases = new String[]{	    
		    "Escherichia-coli-4289-protein-sequences.aa",
		    "InfluenzaA-Avian-All-42334-protein-sequences.fa",
		    "InfluenzaA-Human-Jan-2004-Dic-2008-19745-protein-sequences.fa",
		    "InfluenzaA-Human-Outbreak2009-4777-protein-sequences.fa",
		    "InfluenzaB-Human-All-7672-protein-sequences.fa",
		    "InfluenzaA-Human-Jan-2007-Dic-2008-9620-protein-sequences.fa",
		    "InfluenzaA-Human-Jan-2006-Dic-2008-12325-protein-sequences.fa"
	    };
	    for (int i = 0; i < databases.length; i++) {
		String next = SEQ_HOME + "/data/" + databases[i];
		int nb = DatabaseHandler.generateIndex(next, next + ".idx", null);
	    }
	    check(SEQ_HOME + "/data/" + databases[6]);
	}
	
	static void check(String path) throws Exception {
	    DatabaseHandler handler = DatabaseHandler.instance(path);
	    for (int index=1; index<=handler.getSize(); index++) {
		try {
		    SequenceParser.parse(handler.getSequence(index));		    
		} catch (SequenceParserException e) {
		    System.out.println(handler.getSequence(index));
		    // TODO: handle exception
		}
	    }
	}

}